package org.modelcc.io.java.processor;

import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import org.modelcc.io.java.JavaModelProcessor;
import org.modelcc.io.java.JavaModelReader;
import org.modelcc.io.java.JavaLanguageMetadata;
import org.modelcc.language.metamodel.SimpleLanguageElement;
import org.modelcc.language.metamodel.MemberCollection;
import org.modelcc.language.metamodel.CompositeLanguageElement;
import org.modelcc.language.metamodel.LanguageElement;
import org.modelcc.language.metamodel.LanguageMember;
import org.modelcc.lexer.recognizer.PatternRecognizer;

/**
 * Process defaults elements.
 * When an abstract element matches the empty string, one of its subclasses is its default element.
 */
public class DefaultElementProcessor extends JavaModelProcessor<JavaLanguageMetadata>
{	
	public DefaultElementProcessor (JavaModelReader reader)
	{
		super(reader);
	}
	
	@Override
	public void process(JavaLanguageMetadata metadata) 
	{
    	Set<LanguageElement> elements = metadata.elements;
    	Map<Class,LanguageElement> classToElement = metadata.classToElement;
    	Map<LanguageElement,Set<LanguageElement>> subclasses = metadata.subclasses;
    	Map<LanguageElement, Set<LanguageElement>> defaultElement = metadata.defaultElement; 

    	for (Iterator<LanguageElement> ite = elements.iterator();ite.hasNext();) {
    		LanguageElement e = ite.next();
    		Set<LanguageElement> defaultElements = defaultElement.get(e);
    		if (Modifier.isAbstract(e.getElementClass().getModifiers())) {
    			if (subclasses.containsKey(e)) {
    				for (Iterator<LanguageElement> ites = subclasses.get(e).iterator();ites.hasNext();) {
    					LanguageElement es = ites.next();
    					if (canMatchEmptyString(es,subclasses,classToElement,new HashSet<LanguageElement>())) {
    						if (defaultElements == null)
    							defaultElements = new HashSet<LanguageElement>();
    						defaultElements.add(es);
    					}
    				}
    			}
    		}
    		if (defaultElements != null) {
    			defaultElement.put(e, defaultElements);
    			defaultElements = null;
    		}

    	}
    	for (Iterator<LanguageElement> ite = elements.iterator();ite.hasNext();) {
    		LanguageElement e = ite.next();
    		Set<LanguageElement> defaultElements = defaultElement.get(e);
    		if (defaultElements != null) {
    			if (defaultElements.size()>1) {
    				for (Iterator<LanguageElement> iten = defaultElements.iterator();iten.hasNext();) {
    					log(Level.SEVERE, "In class \"{0}\": Multiple empty matching patterns: {1}.", new Object[]{e.getElementClass().getCanonicalName(), iten.next().getElementClass().getCanonicalName()});
    				}
    			}
    		}
    	}		
    }
	
	
    private boolean canMatchEmptyString(LanguageElement es,Map<LanguageElement,Set<LanguageElement>> subclasses,Map<Class,LanguageElement> classToElement,Set<LanguageElement> history) 
    {
    	if ((es.getPrefix()!=null)) {
    		for (Iterator<PatternRecognizer> ite = es.getPrefix().iterator();ite.hasNext();) {
    			if (ite.next().read("",0) == null) {
    				return false;
    			}
    		}
    	}
    	if ((es.getSuffix()!=null)) {
    		for (Iterator<PatternRecognizer> ite = es.getSuffix().iterator();ite.hasNext();) {
    			if (ite.next().read("",0) == null) {
    				return false;
    			}
    		}
    	}
    	if (SimpleLanguageElement.class.isAssignableFrom(es.getClass())) {
    		return simpleElementCanMatchEmptyString(es);
    	} else if (CompositeLanguageElement.class.isAssignableFrom(es.getClass())) {
    		return compositeElementCanMatchEmptyString(es, subclasses, classToElement, history);
    	} else { 
    		return abstractElementCanMatchEmptyString(es, subclasses, classToElement, history);
    	}
    }

	private boolean abstractElementCanMatchEmptyString(LanguageElement es,
			Map<LanguageElement, Set<LanguageElement>> subclasses,
			Map<Class, LanguageElement> classToElement,
			Set<LanguageElement> history) {

		if (subclasses.containsKey(es)) {
			for (LanguageElement me: subclasses.get(es)) {
				if (!history.contains(me)) {
					Set<LanguageElement> history2 = new HashSet<LanguageElement>();
					history2.addAll(history);
					history2.add(es);
					if (canMatchEmptyString(me,subclasses,classToElement,history2)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	private boolean simpleElementCanMatchEmptyString(LanguageElement es) {
		SimpleLanguageElement bes = (SimpleLanguageElement)es;
		if (bes.getPatternRecognizer().read("",0) != null) {
			return true;
		} else {
			return false;
		}
	}

	private boolean compositeElementCanMatchEmptyString(LanguageElement es,
			Map<LanguageElement, Set<LanguageElement>> subclasses,
			Map<Class, LanguageElement> classToElement,
			Set<LanguageElement> history) {
		CompositeLanguageElement ces = (CompositeLanguageElement)es;
		for (int i = 0;i < ces.getMembers().size();i++) {
			LanguageMember em = ces.getMembers().get(i);
			if (!em.isOptional()) {
				if (em.getPrefix()!=null) {
					for (Iterator<PatternRecognizer> ite = em.getPrefix().iterator();ite.hasNext();) {
						if (ite.next().read("",0) == null) {
							return false;
						}
					}
				}
				if (em.getSuffix()!=null) {
					for (Iterator<PatternRecognizer> ite = em.getSuffix().iterator();ite.hasNext();) {
						if (ite.next().read("",0) == null) {
							return false;
						}
					}
				}
				if (MemberCollection.class.isAssignableFrom(em.getClass())) {
					MemberCollection mem = (MemberCollection)em;
					if (mem.getMinimumMultiplicity()>0) {
						if (em.getSeparator()!=null) {
							for (Iterator<PatternRecognizer> ite = em.getSeparator().iterator();ite.hasNext();) {
								if (ite.next().read("",0) == null) {
									return false;
								}
							}
						}
					}
				}
			}
		}

		boolean anything = false;
		for (int i = 0;i < ces.getMembers().size();i++) {
			LanguageMember em = ces.getMembers().get(i);
			if (!em.isOptional()) {
				Set<LanguageElement> history2 = new HashSet<LanguageElement>();
				history2.addAll(history);
				history2.add(es);
				if (!canMatchEmptyString(classToElement.get(em.getElementClass()),subclasses,classToElement,history2)) {
					anything = true;
				}
			}
		}

		for (int i = 0;i < ces.getMembers().size();i++) {
			LanguageMember em = ces.getMembers().get(i);
			if (!em.isOptional()) {
				LanguageElement emc = classToElement.get(em.getElementClass());
				Set<LanguageElement> history2 = new HashSet<LanguageElement>();
				history2.addAll(history);
				history2.add(es);
				if (!canMatchEmptyString(emc,subclasses,classToElement,history2)) {
					anything = true;
				}
			}
		}
		if (!anything)
			return true;
		else
			return false;
	}
	
}
